/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 1997 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*	Copyright (c) 1988 AT&T	*/
/*	  All Rights Reserved	*/

/*
 * University Copyright- Copyright (c) 1982, 1986, 1988
 * The Regents of the University of California
 * All Rights Reserved
 *
 * University Acknowledgment- Portions of this document are derived from
 * software developed by the University of California, Berkeley, and its
 * contributors.
 */

/*LINTLIBRARY*/

#include	<sys/types.h>
#include	"curses_inc.h"

/*
 * Cursor motion optimization routine.  This routine takes as parameters
 * the screen positions that the cursor is currently at, and the position
 * you want it to be at, and it will move the cursor there very
 * efficiently.  It isn't really optimal, since several approximations
 * are taken in the interests of efficiency and simplicity.  The code
 * here considers directly addressing the cursor, and also considers
 * local motions using left, right, up, down, tabs, backtabs, vertical
 * and horizontal addressing, and parameterized motions.  It does not
 * consider using home down, or taking advantage of automatic margins on
 * any of the four directions.  (Two of these directions, left and right,
 * are well defined by the am and bw capabilities, but up and down are
 * not defined, nor are tab or backtab off the ends.)
 *
 * General strategies considered:
 *	CA	Direct Cursor Addressing
 *	LM	Local Motions from the old position
 *	HR	Home + Local Motions from upper left corner
 *	HDR	Home Down + Local Motions from lower left corner
 *	CR	CR + Local Motions from left margin
 *
 * Local Motions can include
 *	Up	cuu, cuu1, vpa
 *	Down	cud, cud1, vpa
 *	Left	cul, cul1, hpa, bs, cbt
 *	Right	cuf, cuf1, hpa, tab, char moved over
 */

/* This is called _ISMARK2 so it doesn't conflict with _ISMARK1 in wrefresh.c */

#define	_ISMARK2(x)	(mks[(x) / BITSPERBYTE] & (1<<((x) % BITSPERBYTE)))

#define	H_UP	-1
#define	H_DO	1

static	int	Newy;
static  int	_homefirst(int, int, int, int),
		_mvrel(int, int, int, int, int),
		_mvvert(int, int, int), _mvhor(int, int, int),
		_mvright(int, int, int), _mvleft(int, int, int);

int
mvcur(int cury, int curx, int newy, int newx)
{
	int	hu,	/* cost home + relative */
		hd,	/* cost home-down + relative */
		rl,	/* cost relative */
		cm;	/* cost direct cursor motion */

	/* obvious case */
	if (cury == newy && curx == newx)
		return (OK);

	/* not in the right mode for cursor movement */
	if (SP->fl_endwin)
		return (ERR);

	if (!move_standout_mode && curscr->_attrs && !SP->_mks)
		_VIDS(A_NORMAL, curscr->_attrs);

	if (!move_insert_mode && SP->phys_irm)
		_OFFINSERT();

	Newy = newy;

	/* cost of using cm */
	cm = _COST(Cursor_address);

	rl = hd = hu = LARGECOST;

	/* baudrate optimization */
	if (cm < LARGECOST && SP->baud >= 2400 &&
	    cury >= 0 && cury < curscr->_maxy &&
	    curx >= 0 && curx < curscr->_maxx) {
		if (cursor_down && (newy == (cury + 1)) &&
		    ((newx == curx) || (newx == 0 && carriage_return))) {
			if (newx != curx)
				_PUTS(carriage_return, 1);
			_PUTS(cursor_down, 1);
			goto done;
		}

		/* fast horizontal move */
		if (cury == newy && newx < curx - 4 && newx > curx + 4) {
			if (newx < curx)
				rl = _mvleft(curx, newx, FALSE);
			else
				rl = _mvright(curx, newx, FALSE);
			if (rl < cm) {
				if (newx < curx)
					rl = _mvleft(curx, newx, TRUE);
				else
					rl = _mvright(curx, newx, TRUE);
				goto done;
			}
		}
	}

	/* cost using relative movements */
	if (rl >= LARGECOST && cury >= 0 && cury < curscr->_maxy &&
	    curx >= 0 && curx < curscr->_maxx)
		rl = _mvrel(cury, curx, newy, newx, FALSE);

	/* cost of homing to upper-left corner first */
	if (cursor_home)
		hu = _homefirst(newy, newx, H_UP, FALSE);

	/* cost of homing to lower-left corner first */
	if (cursor_to_ll)
		hd = _homefirst(newy, newx, H_DO, FALSE);

	/* can't do any one of them */
	if (cm >= LARGECOST && rl >= LARGECOST && hu >= LARGECOST &&
	    hd >= LARGECOST)
		return (ERR);

	/* do the best one */
	if (cm <= rl && cm <= hu && cm <= hd)
		_PUTS(tparm_p2(cursor_address, newy, newx), 1);
	else
		if (rl <= hu && rl <= hd)
			(void) _mvrel(cury, curx, newy, newx, TRUE);
		else
			(void) _homefirst(newy, newx, hu <= hd ?
			    H_UP : H_DO, TRUE);

done:
	/* update cursor position */
	/*LINTED*/
	curscr->_curx = (short) newx;
	/*LINTED*/
	curscr->_cury = (short) newy;

	return (OK);
}

/* Move by homing first. */

static int
_homefirst(int ny, int nx, int type, int doit)
{
	char	*home;
	int	cy, cost;

	if (type == H_UP) {
		home = cursor_home;
		cost = _COST(Cursor_home);
		cy = 0;
	} else {
		home = cursor_to_ll;
		cost = _COST(Cursor_to_ll);
		cy = curscr->_maxy - 1;
	}

	if (!home)
		return (LARGECOST);
	if (!doit)
		return (cost + _mvrel(cy, 0, ny, nx, FALSE));

	_PUTS(home, 1);
	return (_mvrel(cy, 0, ny, nx, TRUE));
}

/* Move relatively */

static int
_mvrel(int cy, int cx, int ny, int nx, int doit)
{
	int	cv, ch;

	/* do in this order since _mvhor may need the curscr image */
	cv = _mvvert(cy, ny, doit);
	ch = _mvhor(cx, nx, doit);

	return (cv + ch);
}

/* Move vertically */

static int
_mvvert(int cy, int ny, int doit)
{
	char	*ve;
	int	dy, st_1, st_n, cv;

	if (cy == ny)
		goto out;

	/* cost of stepwise movement */
	if (cy < ny) {
		dy = ny-cy;
		st_1 = _COST(Cursor_down) * dy;
		st_n = _COST(Parm_down_cursor);
	} else {
		dy = cy-ny;
		st_1 = _COST(Cursor_up) * dy;
		st_n = _COST(Parm_up_cursor);
	}

	/* cost of using vertical move */
	cv = _COST(Row_address);

	/* if calculating cost only */
	if (!doit)
		return ((cv < st_1 && cv < st_n) ? cv :
		    (st_n < st_1) ? st_n : st_1);

	/* do it */
	if (cv < st_1 && cv < st_n)
		_PUTS(tparm_p1(row_address, ny), 1);
	else
		if (st_n < st_1) {
			if (cy < ny)
				_PUTS(tparm_p1(parm_down_cursor, dy), 1);
			else
				_PUTS(tparm_p1(parm_up_cursor, dy), 1);
		} else {
			if (cy < ny)
				ve = cursor_down;
			else
				ve = cursor_up;
			for (; dy > 0; --dy)
				_PUTS(ve, 1);
		}

out:
	return (0);
}

/* Move horizontally */

static int
_mvhor(int cx, int nx, int doit)
{
	int	st, ch, hl;

	if (cx == nx)
		goto out;

	/* cost using horizontal move */
	ch = _COST(Row_address);

	/* cost doing stepwise */
	st = cx < nx ? _mvright(cx, nx, FALSE) : _mvleft(cx, nx, FALSE);

	/* cost homeleft first */
	hl = (_COST(Carriage_return) < LARGECOST) ?
	    _COST(Carriage_return) + _mvright(0, nx, FALSE) : LARGECOST;

	if (!doit)
		return ((ch < st && ch < hl) ? ch : (hl < st ? hl : st));

	if (ch < st && ch < hl)
		_PUTS(tparm_p1(column_address, nx), 1);
	else
		if (hl < st) {
			_PUTS(carriage_return, 1);
			(void) _mvright(0, nx, TRUE);
		} else {
			if (cx < nx)
				(void) _mvright(cx, nx, TRUE);
			else
				(void) _mvleft(cx, nx, TRUE);
	}
out:
	return (0);
}

/* Move right. */

static int
_mvright(int cx, int nx, int doit)
{
	chtype	*scp;
	char	*mks;
	int	nt, tx, x, stcost, iscont;

	if (!cursor_right && !parm_right_cursor)
		return (LARGECOST);

	scp = curscr->_y[Newy];
	mks = magic_cookie_glitch >= 0 ? SP->_mks[Newy] : NULL;

	if (cursor_right) {
		/* number of tabs used in stepwise movement */
		nt = tab ? (nx / TABSIZE - cx / TABSIZE) : 0;
		tx = (nt > 0) ? (cx / TABSIZE + nt) * TABSIZE : cx;

		/* calculate stepwise cost */
		stcost = nt * _COST(Tab);
		iscont = 0;
		for (x = tx; x < nx; ++x) {
			if (iscont == 0 && !ISCBIT(scp[x]))
				iscont = 1;
			if ((!ceol_standout_glitch && !mks &&
			    _ATTR(scp[x]) == curscr->_attrs) ||
			    ceol_standout_glitch || (mks && !_ISMARK2(x))) {
				if (!ISMBIT(scp[x]))
					stcost += 1;
				else if (iscont && !(nx - x == 1 && nx <
				    curscr->_maxx && ISCBIT(scp[nx])))
					stcost += 1;
				else
					stcost += _COST(Cursor_right);
			} else
				stcost += _COST(Cursor_right);
		}
	} else
		stcost = LARGECOST;

	if (!doit)
		return ((_COST(Parm_right_cursor) < stcost) ?
		    _COST(Parm_right_cursor) : stcost);

	/* actually move */
	if (_COST(Parm_right_cursor) < stcost)
		_PUTS(tparm_p1(parm_right_cursor, nx-cx), 1);
	else {
		if (SP->phys_irm)
			_OFFINSERT();
		for (; nt > 0; --nt)
			_PUTS(tab, 1);
		iscont = 0;
		for (x = tx; x < nx; ++x) {
			if (iscont == 0 && !ISCBIT(scp[x]))
				iscont = 1;
			if ((!ceol_standout_glitch && !mks &&
			    _ATTR(scp[x]) == curscr->_attrs) ||
			    ceol_standout_glitch || (mks && !_ISMARK2(x))) {
				if (!ISMBIT(scp[x]))
					(void) _outwch(_CHAR(scp[x]));
				else if (iscont && !(nx - x == 1 &&
				    nx < curscr->_maxx && ISCBIT(scp[nx])))
					(void) _outwch(_CHAR(scp[x]));
				else
					_PUTS(cursor_right, 1);
			} else
				_PUTS(cursor_right, 1);
		}
	}

	return (0);
}

/* Move left */

static int
_mvleft(int cx, int nx, int doit)
{
	int	tx, nt, x, stcost;

	if (!cursor_left && !parm_left_cursor)
		return (LARGECOST);

	if (cursor_left) {
		/* stepwise cost */
		tx = cx;
		nt = 0;
		if (back_tab) {
			/* the TAB position >= nx */
			x = (nx % TABSIZE) ? (nx / TABSIZE + 1) * TABSIZE : nx;

			/* # of tabs used and position after using them */
			if (x < cx) {
				nt = (cx / TABSIZE - x / TABSIZE) +
				    ((cx % TABSIZE) ? 1 : 0);
				tx = x;
			}
		}
		stcost = nt * _COST(Back_tab) + (tx-nx) * _COST(Cursor_left);
	} else
		stcost = LARGECOST;

	/* get cost only */
	if (!doit)
		return ((_COST(Parm_left_cursor) < stcost) ?
		    _COST(Parm_left_cursor) : stcost);

	/* doit */
	if (_COST(Parm_left_cursor) < stcost)
		_PUTS(tparm_p1(parm_left_cursor, cx - nx), 1);
	else {
		for (; nt > 0; --nt)
		    _PUTS(back_tab, 1);
		for (; tx > nx; --tx)
		    _PUTS(cursor_left, 1);
	}

	return (0);
}
